<?php

class Token
{
    const EOF = 0;
    const INTEGER = 1;
    const PLUS = 2;
    const MINUS = 3;
    const MUL = 4;
    const DIV = 5;
    const LEFT_PARENTHESE = 6;
    const RIGHT_PARENTHESE = 7;

    public $type;
    public $value;

    public function __construct($type, $value)
    {
        $this->type = $type;
        $this->value = $value;
    }

    public function tokenName($type)
    {
        $name = 'Unknown';
        switch ($type) {
            case self::INTEGER:
                $name = 'INTEGER';
                break;
            case self::PLUS:
                $name = 'PLUS';
                break;
            case self::MINUS:
                $name = 'MINUS';
                break;
            case self::MUL:
                $name = 'MUL';
                break;
            case self::DIV:
                $name = 'DIV';
                break;
            case self::LEFT_PARENTHESE:
                $name = 'LEFT_PARENTHESE';
                break;
            case self::RIGHT_PARENTHESE:
                $name = 'RIGHT_PARENTHESE';
                break;
        }

        return $name;
    }

    public function __toString()
    {
        $type = $this->tokenName($this->type);

        return "Token({$type}, {$this->value})";
    }
}

class Interpreter
{
    private $text;
    private $position;

    public function __construct($text)
    {
        $this->text = $text;
        $this->position = 0;
        $this->token = null;
    }

    public function fetchChar()
    {
        if ($this->position >= strlen($this->text)) {
            return null;
        }

        return $this->text[$this->position];
    }

    public function pushToken($token)
    {
        $this->token = $token;
    }

    public function fetchToken()
    {
        if ($this->token != null) {
            $token = $this->token;
            $this->token = null;

            return $token;
        }

        $char = $this->fetchChar();
        while ($char == ' ' || $char == "\t") {
            $this->position++;
            $char = $this->fetchChar();
        }

        if ($char == null) {
            return new Token(Token::EOF, null);
        }

        // 这里已跳过全部空白, 现在取出一个 token
        if ($char == '+') {
            $this->position++;
            return new Token(Token::PLUS, '+');
        }

        if ($char == '-') {
            $this->position++;
            return new Token(Token::MINUS, '-');
        }

        if ($char == '*') {
            $this->position++;
            return new Token(Token::MUL, '*');
        }

        if ($char == '/') {
            $this->position++;
            return new Token(Token::DIV, '/');
        }

        if ($char == '(') {
            $this->position++;
            return new Token(Token::LEFT_PARENTHESE, '(');
        }

        if ($char == ')') {
            $this->position++;
            return new Token(Token::RIGHT_PARENTHESE, ')');
        }

        if (is_numeric($char)) {
            $number = 0;
            while (is_numeric($char)) {
                $number = $char + ($number * 10);
                $this->position++;
                $char = $this->fetchChar();
            }

            return new Token(Token::INTEGER, $number);
        }

        throw new \Exception("Unknow lexeme: {$char}");
    }

    private function eat($token, $type)
    {
        if ($token->type != $type) {
            $type = $token->tokenName($type);
            throw new \Exception("expected token: {$type} get bad token: " . $token);
        }
    }

    /**
     * factor: INTEGER | '(' expr ')'
     */
    public function factor()
    {
        $factor = $this->fetchToken();
        if ($factor->type == Token::LEFT_PARENTHESE) {
            $result = $this->expr();
            $this->eat($this->fetchToken(), Token::RIGHT_PARENTHESE);
        } else {
            $this->eat($factor, Token::INTEGER);
            $result = $factor->value;
        }

        return $result;
    }

    /**
     * expr: term ((PLUS | MINUS) term)*
     */
    public function expr()
    {
        $result = $this->term();

        $token = $this->fetchToken();
        while (in_array($token->type, [Token::PLUS, Token::MINUS])) {
            $term = $this->term();
            if ($token->type == Token::PLUS) {
                $result += $term;
            } else if ($token->type == Token::MINUS) {
                $result -= $term;
            }
            $token = $this->fetchToken();
        }
        $this->pushToken($token);

        return $result;
    }

    /**
     * term: factor ((MUL | DIV) factor)*
     */
    public function term()
    {
        $result = $this->factor();

        $token = $this->fetchToken();
        while (in_array($token->type, [Token::MUL, Token::DIV])) {
            $factor = $this->factor();
            if ($token->type == Token::MUL) {
                $result *= $factor;
            } else if ($token->type == Token::DIV) {
                $result /= $factor;
            }

            $token = $this->fetchToken();
        }
        $this->pushToken($token);

        return $result;
    }
}


while (true) {
    $text = readline("cal> ");
    if ($text == "q" || $text == "") {
        echo "Bye!\n";
        break;
    }

    $interperter = new Interpreter($text);
    $result = $interperter->expr();

    echo $result . "\n";
}
